Skip to content

fix(driver-sql): a date field still on a date-time column filters as its calendar day on SQLite and MySQL; operator note for Postgres days stored early - #33270

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-lta7t3
Nov 14, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-lta7t3

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #33002
Fixes #33001

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:SQLite 和 MySQL 上,字段从「日期时间」改成「日期」并发布后,旧行仍存着带时分秒的值;现在按日期过滤时把这类列读成它的日历日,旧行也能被「等于某天」筛出来。另附一段运维说明(driver-sql (Postgres): days written a day early into a datetime-to-date column before #32851 stay a day early — repair or not #33001):Postgres 上 18.4.0 之前存早了一天的日期如何查找和手工修正,平台不自动修。
  • 为什么改:改成日期字段后,列表按「11 月 12 日到期」筛选会悄悄漏掉改字段之前创建的记录,虽然它们显示的就是 11 月 12 日;而按「晚于 12 日」筛选反而会把它们选出来。
  • 风险与回滚:只影响「日期字段 + 列仍是日期时间类型」这一种组合(只有日期时间改日期的发布会留下);写入、读取、按天分组不变;这种列上的索引不再服务该字段的过滤(Postgres 已是如此)。回滚即 revert。
  • 先看:packages/drivers/driver-sql/src/sql-driver-filter-helpers.ts 的 filterColumnExpr 新分支,其次 sql-driver-rotation.ts 登记这类列的那段,再看 date-on-instant-column.ts。
  • 两分钟验证:pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-32851-date-on-instant-column.test.ts,预期 SQLite 格全绿(MySQL/Postgres 格在 CI 的 Temporal Conformance 任务里跑)。
  • 需要你做的:无,区域线程落地。

What changes

#33002. A datetime → date publish leaves the column's date-time type in place on every dialect (the narrowing would drop the time of day, so it is not applied). Postgres got a read rule in #33000; on SQLite (datetime, canonical UTC text) and MySQL (DATETIME, UTC session) the zone was never wrong, but a row written while the field was a datetime still holds its time of day. It reads as its day, yet due = '2026-11-12' compared the bare day with the whole instant and missed it, and due > '2026-11-12' matched it.

  • isPostgresInstantColumnType becomes isInstantColumnType(dialect, type): Postgres timestamp with time zone; MySQL datetime / timestamp; SQLite datetime. The schema sync records such date fields on every dialect (sql-driver-rotation.ts), from the columnInfo() it already reads; a table created here still records none.
  • filterColumnExpr and its outside-the-builder twin temporalFilterColumnSql read such a column as its calendar day per dialect: SQLite substr(CANONICAL_UTC_TEXT, 1, 10) (through sqliteCanonicalDatetimeSql, so a legacy epoch value answers its UTC day too); MySQL date(col); Postgres unchanged.
  • The write keeps the bare day on SQLite and MySQL (it already is UTC midnight there); only Postgres binds the UTC-midnight instant, as before. The day bucket already answered the day on both (strftime / date_format), unchanged.

#33001 (maintainer ruling 15:05Z: no automatic repair; release-notes text on how an operator finds and corrects affected rows). A patch changeset on @objectstack/driver-sql carries the note: who is affected, a query that lists candidates, and the UPDATE that moves a confirmed early day to its UTC midnight. No code.

Tests

packages/drivers/driver-sql/src/sql-driver-32851-date-on-instant-column.test.ts (live dialect matrix): the datetime-era row's read, equality filter and day bucket are now asserted on every cell, not Postgres only; $lt excludes it and $gt on the day finds nothing; the analytics filter door (temporalFilterColumnSql + temporalFilterValue) answers the same three rows on every cell (the bucket echo stays Postgres-only).

Measurement and ablation (SQLite, local)

Before, at e540baf8, a scratch probe (removed): after syncSchema V1 (due: datetime) → row due: 2026-11-12T09:30:00.000Z → syncSchema V2 (due: date) + applyPublishedColumnChanges (applied: []): column type datetime, raw 2026-11-12T09:30:00.000Z, read 2026-11-12, where: { due: '2026-11-12' } → only the row written after the publish; the day bucket already 2026-11-12 × 2.

Ablation (registry population on SQLite/MySQL removed, sql-driver-rotation.ts and date-on-instant-column.ts restored from origin/main): 3 failed — equality/range ($gt found era), the datetime-era row (['created','updated'] vs ['created','era','updated']), the analytics filter door (same). Restored by git checkout HEAD -- packages/drivers/driver-sql/src/, git status --porcelain empty, file green again.

Suites and gates
  • pnpm --filter @objectstack/driver-sql exec vitest run: Test Files 253 passed | 13 skipped (266); Tests 4256 passed | 237 skipped (4493).
  • pnpm --filter @objectstack/driver-sql typecheck: exit 0 (after a declaration build of the closure).
  • node scripts/pm/dispatch-gates.mjs --commands → --ran: 64 families; 62 ran green locally. Not green locally: plugin-teardown-shape self-test and dual-build-cjs-loads (exit 3, prerequisite not met: they need a full build), dts-closure (exit 1, same full-build prerequisite), query-options-erasure and type-check-debt (stopped at a 300 s local timeout). CI Lint & Repo Gates, TypeScript Type Check and Build Core are green on this head.
  • NOT MEASURED: the MySQL and Postgres cells. There are no servers in this container, and CI's Temporal Conformance (live PG + MySQL) job runs only on push to main (it is skipped on pull_request and merge_group), so those cells are first measured by the post-merge main run.

Acceptance notes

  • As on Postgres, an index on such a column no longer serves a filter on the field (it is read through an expression). Only columns a datetime → date publish left behind are affected.
  • The registry is filled by the schema sync; a table the driver never syncs (deferred DDL, a guest datasource) keeps the plain date path.

Verification

Branch merged origin/main once before the PR (44c8aa3b; no driver-sql change came with it).
Both cards carry a Claim: naming claude/project-thread-lta7t3 (#33002, #33001).

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cw787kMeaX7oyz7DDC


Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql, touching 27 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectql/query-syntax.mdx (via storageDatetimeValue (literal, a string literal in formatInput))
What this run could not see
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d4fbb4dbb655f1d1527eabeea0ef3af64f152663 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 597cbbf444588644d893910ece536afa0f7bff1c — the merge of head 654326d01f6321651ac9b31120001b5146824671 into base d4fbb4dbb655f1d1527eabeea0ef3af64f152663, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 597cbbf444588644d893910ece536afa0f7bff1c && git checkout 597cbbf444588644d893910ece536afa0f7bff1c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d4fbb4dbb655f1d1527eabeea0ef3af64f152663 654326d01f6321651ac9b31120001b5146824671 && git checkout -B drift-repro d4fbb4dbb655f1d1527eabeea0ef3af64f152663 && git merge --no-ff 654326d01f6321651ac9b31120001b5146824671

node scripts/docs-audit/affected-docs.mjs --json d4fbb4dbb655f1d1527eabeea0ef3af64f152663

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d4fbb4dbb655f1d1527eabeea0ef3af64f152663 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cw787kMeaX7oyz7DDC

Request: review · area A4 · head 654326d

Fixes #33002 #33001 · Clause-② no · Metadata-protocol none · 7 files, +139/−40. CI green at this head (Temporal Conformance runs on push to main only; the MySQL/Postgres cells are NOT MEASURED before merge — see the body).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract-review-claim: session_016WV4czuuneAppDnJbrNejw · head 654326d · 2026-11-14T15:46Z

review#2 (server area pilot, A4).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Review: PASS

Head: 654326d01f6321651ac9b31120001b5146824671 · reviewer review#2 (session_016WV4czuuneAppDnJbrNejw) · area A4 · Fixes #33002 #33001

Clause-② no is right: no authorable key, value, type or refusal moves. #33002 makes an equality/range filter on a date field left on an instant column match the day the row already reads as; #33001 is an operator note only.

Checked:

Notes (no change needed):

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cw787kMeaX7oyz7DDC

ACCEPT · area A4 · head 654326d · 2026-11-14T15:49Z

  • Review: PASS by review#2 (comment 6880061836) at this head.
  • check-governed-merges --pr 33270: NOT governed, 179 changed lines.
  • Checks green at this head; Temporal Conformance (live PG + MySQL) runs on push to main only, so the MySQL/PG cells are read on the post-merge main run. A red MySQL/PG cell there is a p1 follow-up for area A4.
  • Ready + auto-merge under the maintainer's 15:21Z go for area A4.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 14, 2026 15:50
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 14, 2026
Merged via the queue into main with commit 01d206d Nov 14, 2026
58 of 60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lta7t3 branch November 14, 2026 16:20
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

Path: none

Triage: main push CI went red after this landed · 2026-11-14T16:47Z

The Temporal Conformance (live PG + MySQL) job fails this PR's new test sql-driver-32851-date-on-instant-column.test.ts on live MySQL (the native-SQL filter door answers [undefined, undefined]). That job runs only on push, not in merge_group. It was red on 6a28a271 and 24f2f401, and green on 71ade1ae before this landed. Filed as #33324 (p1, Area A4). Please do not quarantine the cell.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants